
/* 
 * This file is part of FreeBMS - an open source battery management system
 *
 * Serial Functions
 * 
 * NOTE: This code is designed to be compiled using SOURCEBOOST (http://www.sourceboost.com)
 */
 
/* This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
/*************************************************************
* Blocking serial interface code for the hardware UART
* of PIC16-range microcontrollers from Microchip
*
* (c) Lieven Hollevoet
* most recent version on http://boostc.lika.be
* Based on code from Yann Hendrikx.
*************************************************************/

#include "types.h"
#include "canbus.h"
#include "serial.h"
#include "struct.h"
#include "codes.h"
#include "dtc.h"

// Serial initialization
void serial_init(char brg)
{
	// Transmit control register
	txsta.TX9     = 0;
	txsta.TXEN    = 1;
	txsta.SYNC    = 0;
	txsta.BRGH    = 0; 
	
	// Receive control register
	rcsta.SPEN    = 1;
	rcsta.RX9     = 0;
	rcsta.CREN    = 1;
	rcsta.ADDEN   = 0;

	// Baudcon register, if supported by the target architecture
	baudcon.BRG16 = 0;
	baudcon.WUE   = 0;
	baudcon.ABDEN = 0;

	// Baud rate setting
    spbrg 	   = brg;	
 }

// Print a single character to the serial port
void serial_printf(char value)
{
    while((txsta & 1 << TRMT) == 0); 
    txreg = value;
}

// Read a character from the serial port
char serial_getch(){
	// Check for overrun error. Reset the UART if any.
	if (rcsta.OERR){
		rcsta.CREN = 0;
		rcsta.CREN = 1;
	}
	
	while (!serial_peek());
	return rcreg;
}

/* 
 * 
 * START FREEBMS CHANGES 
 *
 */

bool rxescape;					// Whether previous character was escape character
bool rxblock_bit;				// Whether we are blocking for a message
BYTE rxblock_from;				// Expecting a message from this cell ID
BYTE rxblock_type;				// Expecting a message of this message ID
BYTE rxblock_timer;				// Timer for RX timeout
BYTE rxbuffer[MAX_MESG_SIZE];	// Buffer for sending messages
BYTE rxptr;						// Pointer for receiving messages
BYTE rxlength;					// Length of message being received
BYTE chip_id;					// ID of current chip

extern BYTE comm_errors;			// Communication errors
extern unsigned short timer_errors; // Error timeout timer
extern BYTE operation_mode;			// Chip operation mode

#if DEBUG==1
#warning "Debugging has been enabled"
unsigned char r0, r1, r2, r3, r4, r5, r6, r7;
unsigned char t0, t1, t2, t3, t4, t5, t6, t7;

// Set t0-t7 to txbuffer for debugging
void txbuffer_to_vars(char *txbuffer) {
	t0 = txbuffer[0];
	t1 = txbuffer[1];
	t2 = txbuffer[2];
	t3 = txbuffer[3];
	t4 = txbuffer[4];
	t5 = txbuffer[5];
	t6 = txbuffer[6];
	t7 = txbuffer[7];
}

// Set r0-r7 to rxbuffer for debugging
void rxbuffer_to_vars() {
	r0 = rxbuffer[0];
	r1 = rxbuffer[1];
	r2 = rxbuffer[2];
	r3 = rxbuffer[3];
	r4 = rxbuffer[4];
	r5 = rxbuffer[5];
	r6 = rxbuffer[6];
	r7 = rxbuffer[7];
}

#endif

// Send a serial message - main function
BYTE SerialSendMessage(short mesg_type, short dest_from, BYTE *mesg_buf, BYTE length, BYTE rxblock) {
	unsigned int checksum = 0;
	struct cell_data *tmp_ptr;
	BYTE txbuffer[MAX_MESG_SIZE];
	BYTE txlength = 0;
	BYTE i = 0;
	
	// Don't transmit a message that is too long
	if(length > MAX_DATA_SIZE) 
		return false;
	
	// create the message
	txbuffer[0] = 0xFF;				// Message start
	txbuffer[1] = length + 4;		// Length of message + length + id + type + checksum
	txbuffer[2] = dest_from;		// Sender ID or Destination ID
	txbuffer[3] = mesg_type;		// Type of message
	for(i = 0; i < length; ++i)		// Data
		txbuffer[i + 4] = mesg_buf[i];

	txlength = length + 4;
						
	for(i = 0; i < txlength; ++i) {
		checksum += (BYTE) txbuffer[i];

		// Send escape character if necessary
		if((txbuffer[i] == 0xFF && i != 0) || txbuffer[i] == 0xFE)
			serial_printf(0xFE);

		serial_printf(txbuffer[i]);	
	}

	// Cut the checksum to 1 byte
	checksum &= 0xFF;

	// Send escape character if necessary
	if(checksum == 0xFF || checksum == 0xFE)
		serial_printf(0xFE);
	serial_printf(checksum);

#if DEBUG==1
	txbuffer[length + 4] = checksum;
	txbuffer_to_vars(txbuffer);
#endif

	// If we are expecting a response message
	if(rxblock == true) {
		// Block for serial receive
		rxblock_from = dest_from; // Set that we are expecting a message from target chip
		rxblock_type = mesg_type; // Set that we are expecting a message of 'mesg_type' ID
		rxblock_bit = true;	// Set that we are expecting a message in return
		rxblock_timer = 2;  // Set the timeout timer for 2 ticks - (10 ticks ~ 1 second)

		// loop until we unblock or timeout
		while((rxblock_bit == true) && (rxblock_timer > 0)) { } 
			
		// If we timed out
		if(rxblock_bit == true) {
			// Clear watchdog timer
			clear_wdt();
			
			// We timed out - increment error counter for cell
			for(tmp_ptr = first_cell; tmp_ptr; tmp_ptr = tmp_ptr->next) {
				if(tmp_ptr->id == dest_from) {
					// Add to comm error count for specific cell
					tmp_ptr->comm_errors++;
					break;
				}
			}

			// Retry the message again:
			for(i = 0; i < txlength; ++i) {
				// Send escape character if necessary
				if((txbuffer[i] == 0xFF && i != 0) || txbuffer[i] == 0xFE)
					serial_printf(0xFE);

				serial_printf(txbuffer[i]);	
			}
	
			// Send escape character if necessary
			if(checksum == 0xFF || checksum == 0xFE)
				serial_printf(0xFE);
			serial_printf(checksum);

			rxblock_bit = true;	// Set that we are expecting a message back
			rxblock_from = dest_from; // Set that we are expecting a message from target chip
			rxblock_type = mesg_type; // Set that we are expecting a message of 'mesg_type' ID
			rxblock_timer = 3;  // Set a longer timer this time

			while((rxblock_bit == true) && (rxblock_timer > 0)) { }
				
			if(rxblock_bit == true) {
				// Clear watchdog timer
				clear_wdt();

				// We timed out again - increment error counter for cell
				for(tmp_ptr = first_cell; tmp_ptr; tmp_ptr = tmp_ptr->next) {
					if(tmp_ptr->id == dest_from) {
						// Add to comm error count for specific cell
						tmp_ptr->comm_errors++;
						break;
					}
				}

				// Increment global comm_error counter if we are in normal mode
#warning "This should eventually be fixed."
				if(operation_mode == 0)
					++comm_errors;
				
				// Start the timeout timer for errors
				if(timer_errors <= 0)
					timer_errors = TIMER_ERRORS;

				// Create DTC if too many errors occurred
				if(comm_errors >= MAX_COMM_ERRORS) {
					txbuffer[0] = ERROR_COMM_ERRORS;
					txbuffer[1] = DTC_PRIORITY_MED;
					txbuffer[2] = comm_errors;
					txbuffer[3] = tmp_ptr->id;
					txbuffer[4] = tmp_ptr->comm_errors;

					// Create / Update DTC
					update_dtc(5, txbuffer);
				}
				
				// Reset the rxblock_bit
				rxblock_bit = false;

				// Return false to indicate the failure.
				return false;
			} 
		}
	} else {
		rxblock_timer = 0;
		rxblock_bit = false;
	}
	
	// Return true if we were able to send the message successfully
	return true;
}

// Receive a message using interrupts
BYTE SerialReceiveMessage() {
	BYTE input;
	int i = 0, checksum = 0;

	if(serial_peek()) { // If there is a character pending delivery
		input = serial_getch();

		// If its '0xFF' and no escape character, start of new message
		if(input == 0xFF && rxescape == false) {
			rxptr = 0;
			rxlength = 0;

		} else {
			// If we got an escape sequence, set flag and continue
			if(input == 0xFE && rxescape == false) {
				rxescape = true;
			} else { // Else store character
				if(rxptr == 0) {
					rxlength = input;
				} 

				rxbuffer[rxptr] = input;
				++rxptr;
			}
		}
		
		// If rxptr is equal to or longer than the expected length, or ptr is invalid
		if((rxptr >= rxlength && rxptr > 0)
		|| rxptr >= MAX_MESG_SIZE - 1) {

			// Checksum for data verification
			checksum = 0xFF;
			for(i = 0; i < rxptr-1; ++i)
				checksum += (unsigned short) rxbuffer[i];
			checksum &= 0xFF;

			// If checksums don't match, discard the message
			if(checksum != rxbuffer[rxptr-1]) {
				rxptr = 0;
				rxlength = 0;
				rxescape = false;

				// Return false to indicate a failed or garbled message
				return false;
			} else {
				rxptr = 0;
				rxlength = 0;
				rxescape = false;
#if DEBUG==1
				rxbuffer_to_vars();
#endif
				// If the message received matches message type and origin, clear rxblock
				if(rxblock_bit == true) {
					if(rxbuffer[1] == rxblock_from
					&& rxbuffer[2] == rxblock_type) {
						// Return true indicating the message was received successfully
						rxblock_bit = false;
						rxblock_timer = 0;
						return true;
					} else {
						// Return false indicating the message was garbled
						return false;
					}
				} else {
					// Return true indicating the message was received successfully
					return true;
				}
			}
		}
	}
	
	// If we got here, return false indicating the message isn't yet fully received
	return false;
}